/*
 * Copyright (C) 2013 Daniel Wasilew <daniel.wasilew@codeminer.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.codeminer.parts.specification.source;

import de.wusel.picotask.xml.element.XMLElement;
import org.codeminer.parts.specification.CreationException;
import org.codeminer.parts.specification.DocCreator;
import org.codeminer.parts.specification.ParserFileLocations;
import org.codeminer.parts.specification.general.EmptyLinePart;
import org.codeminer.parts.specification.general.FilePart;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.*;

/**
 * @author Daniel Wasilew <daniel.wasilew@codeminer.org>
 */
public class ActionFactoryCreator implements DocCreator {

    private static final Logger log = LoggerFactory.getLogger(ActionFactoryCreator.class);
    private static final String SPACE = "    ";
    private final EmptyLinePart emptyLinePart = new EmptyLinePart();
    private final PackagePart packagePart = new PackagePart();
    private final CommentPart commentPart = new CommentPart();
    private final FactoryClassDefinition factoryClassDefinition = new FactoryClassDefinition();
    private PrintWriter fileWriter;
    private final ParserFileLocations parserFileLocations;

    private final Set<String> importStatements = new HashSet<>();
    private final List<XMLElement> parsedElements = new LinkedList<>();

    public ActionFactoryCreator(ParserFileLocations parserFileLocations) {
        this.parserFileLocations = parserFileLocations;
    }

    @Override
    public void start() throws CreationException {
        try {
            File outputFile = new File(parserFileLocations.getJavaSourceOutputDirectory(), "ActionFactory.java");
            if (outputFile.exists()) {
                outputFile.delete();
            }
            outputFile.createNewFile();
            fileWriter = new PrintWriter(outputFile);


        } catch (IOException ex) {
            throw new CreationException(ex);
        }
    }

    @Override
    public void create(XMLElement element, File file) throws CreationException {
        this.parsedElements.add(element);

        for (XMLElement paramElement : element.getChild("input").getChildren()) {
            String type = paramElement.getAttributes().getString("type");
            switch (type) {
                case "String":
                    break;
                case "Boolean":
                    break;
                case "Long":
                    break;
                case "UUID":
                    this.importStatements.add("import java.util.UUID;");
                    break;
                default:
                    throw new IllegalArgumentException("unhandled parameter type [" + type + "]");
            }
        }
    }

    @Override
    public void end() throws CreationException {
        packagePart.writeTo(fileWriter, null);
        emptyLinePart.writeTo(fileWriter, null);
        if (!this.importStatements.isEmpty()) {
            for (String importStatement : importStatements) {
                fileWriter.println(importStatement);
            }
            emptyLinePart.writeTo(fileWriter, null);
        }
        commentPart.writeTo(fileWriter, null);
        emptyLinePart.writeTo(fileWriter, null);
        factoryClassDefinition.writeTo(fileWriter, null);

        ActionBlockWriter actionBlockWriter =new ActionBlockWriter();
        for (XMLElement parsedElement : parsedElements) {
            actionBlockWriter.writeTo(fileWriter, parsedElement);
        }
        new FactoryEndWriter().writeTo(fileWriter, null);
        fileWriter.close();
    }

    private static final class FactoryClassDefinition implements FilePart {

        @Override
        public void writeTo(PrintWriter writer, XMLElement parsedElement) {
            writer.println("/**");
            writer.println(" * Generated ActionFactory.");
            writer.println(" * WARNING! Do NOT modify this class");
            writer.println(" * It is always regenerated by the Specparser.");
            writer.println(" * All your changes will be lost the next time it is executed!");
            writer.println(" **/");
            writer.println("public final class ActionFactory {");
            writer.println();
            writer.println(SPACE + "public static Action create(ActionContext context, ActionProperties actionProperties) throws ActionCreationException {");
            writer.println(SPACE + SPACE + "String actionName = actionProperties.getActionName();");
            writer.println(SPACE + SPACE + "try{");
        }
    }

    private static final class FactoryEndWriter implements FilePart {

        @Override
        public void writeTo(PrintWriter writer, XMLElement parsedElement) {
            writer.println(SPACE + SPACE + SPACE + "throw new UnsupportedOperationException(\"Message not handled\");");
            writer.println(SPACE + SPACE + "} catch (Exception ex) {");
            writer.println(SPACE + SPACE + SPACE + "throw new ActionCreationException(ex);");
            writer.println(SPACE + SPACE + "}");
            writer.println(SPACE + "}");
            writer.println("}");
        }
    }

    private final class ActionBlockWriter implements FilePart {

        @Override
        public void writeTo(PrintWriter writer, XMLElement parsedElement) {
            XMLElement classElement = parsedElement.getChild("class");
            String className = classElement.getAttributes().getString("name");

            log.info("adding block for [{}]", className);
            StringBuilder paramExtract = new StringBuilder();
            StringBuilder parameterCall = new StringBuilder();
            int index = 0;
            for (XMLElement paramElement : parsedElement.getChild("input").getChildren()) {
                String type = paramElement.getAttributes().getString("type");
                String name = paramElement.getAttributes().getString("name");
                paramExtract.append(SPACE + SPACE + SPACE + SPACE).
                        append(type).
                        append(" param").
                        append(index).
                        append(" = ");
                switch (type) {
                    case "String":
                        paramExtract.append("actionProperties.getParameter(\"").append(name).append("\");");
                        break;
                    case "Boolean":
                        paramExtract.append("actionProperties.getBooleanParameter(\"").append(name).append("\");");
                        break;
                    case "Long":
                        paramExtract.append("actionProperties.getLongParameter(\"").append(name).append("\");");
                        break;
                    case "UUID":
                        paramExtract.append("actionProperties.getUUIDParameter(\"").append(name).append("\");");
                        break;
                    default:
                        throw new IllegalArgumentException("unhandled parameter type [" + type + "]");
                }

                paramExtract.append("\n");
                parameterCall.append(", ");
                parameterCall.append("param").append(index);
                index++;
            }

            writer.println(SPACE + SPACE + SPACE + "if (\"" + className + "\".equals(actionName)) {");

            writer.println(paramExtract.toString());
            writer.println(SPACE + SPACE + SPACE + SPACE + "return new " + className + "(context" + parameterCall.toString() + ");");
            writer.println(SPACE + SPACE + SPACE + "}");
        }
    }
}
